package com.inepex.inechart.client.interactivity;

import java.util.ArrayList;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasAllMouseHandlers;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.dom.client.MouseWheelEvent;
import com.google.gwt.event.dom.client.MouseWheelHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Widget;
import com.inepex.inechart.client.interactivity.FilteredDragEvent.DragEventType;

/**
 * 
 * Class for handling and managing user-source DOM events.
 * 
 * @author Miklos Suveges / Inepex Ltd.
 *
 */
public class UIEventManager implements
MouseDownHandler, MouseOutHandler, MouseMoveHandler, MouseOverHandler, MouseUpHandler, ClickHandler,
HasAllMouseHandlers, HasClickHandlers{

	protected Widget eventSource;
	protected ArrayList<HandlerRegistration> selfHRs;
	/**
	 * manages events for chart's interactive components
	 */
	protected HandlerManager handlerManager;
	protected boolean ignoreAllEvents;

	protected boolean mouseOver;
	protected boolean mouseDown;
	protected int[] mouseDownCoords;
	protected int[] lastMouseMoveCoords;
	protected int[] lastFilteredCoords;
	protected int dragFilterTimeout;
	protected boolean dragFilterInProgress;
	public static final int defaultDragFilterTimeout = 40;
	protected RepeatingCommand dragFilterCommand = new RepeatingCommand() {

		@Override
		public boolean execute() {
			if(mouseDown &&
					lastFilteredCoords != lastMouseMoveCoords
					//					(lastFilteredCoords[0] != lastMouseMoveCoords[0] || lastFilteredCoords[1] != lastMouseMoveCoords[1])
					){
				fireEvent(new FilteredDragEvent(DragEventType.To, lastMouseMoveCoords));
				lastFilteredCoords = lastMouseMoveCoords;
			}
			return mouseDown;
		}
	};


	public UIEventManager(Widget eventSource) {
		selfHRs = new ArrayList<HandlerRegistration>();
		handlerManager = new HandlerManager(this);
		setEventSource(eventSource);
	}

	protected void removeSelfHRs(){
		for (HandlerRegistration hreg : selfHRs) {
			hreg.removeHandler();
		}
	}

	public void setEventSource(Widget eventSource){
		removeSelfHRs();
		selfHRs.add(eventSource.addDomHandler(this, MouseDownEvent.getType()));
		selfHRs.add(eventSource.addDomHandler(this, MouseUpEvent.getType()));
		selfHRs.add(eventSource.addDomHandler(this, MouseMoveEvent.getType()));
		selfHRs.add(eventSource.addDomHandler(this, ClickEvent.getType()));
		selfHRs.add(eventSource.addDomHandler(this, MouseOutEvent.getType()));
		selfHRs.add(eventSource.addDomHandler(this, MouseOverEvent.getType()));
		this.eventSource = eventSource;
	}

	@Override
	public void fireEvent(GwtEvent<?> event){
		if(!ignoreAllEvents){
			handlerManager.fireEvent(event);
		}
	}

	protected int[] getCoords(MouseEvent<?> event){
		return new int[]{
				event.getRelativeX(eventSource.getElement()),
				event.getRelativeY(eventSource.getElement())};
	}

	public boolean isMouseOverEventSource(){
		return mouseOver;
	}

	private void startDragEventFiltering(){
		Scheduler.get().scheduleFixedPeriod(dragFilterCommand, dragFilterTimeout);
	}

	/*DOM events from source*/	

	@Override
	public void onMouseUp(MouseUpEvent event) {
		event.preventDefault();
		DOM.releaseCapture(eventSource.getElement());
		dragFilterInProgress = mouseDown = false;
		fireEvent(new FilteredDragEvent(DragEventType.End, getCoords(event)));
		fireEvent(event);
	}

	@Override
	public void onMouseOver(MouseOverEvent event) {
		event.preventDefault();
		mouseOver = true;
		fireEvent(event);
	}

	@Override
	public void onMouseMove(MouseMoveEvent event) {
		event.preventDefault();
		lastMouseMoveCoords = getCoords(event);
		if(!dragFilterInProgress && mouseDown){
			fireEvent(new FilteredDragEvent(DragEventType.To, lastMouseMoveCoords));
		}
		fireEvent(event);
	}

	@Override
	public void onMouseOut(MouseOutEvent event) {
		event.preventDefault();
		mouseOver = false;
		fireEvent(event);
	}

	@Override
	public void onMouseDown(MouseDownEvent event) {
		event.preventDefault();
		DOM.setCapture(eventSource.getElement());
		mouseDown = true;
		mouseDownCoords = getCoords(event);
		dragFilterInProgress = dragFilterTimeout > 10;
		if(dragFilterInProgress){
			lastFilteredCoords = getCoords(event);
			startDragEventFiltering();
		}
		fireEvent(new FilteredDragEvent(DragEventType.Start, mouseDownCoords));
		fireEvent(event);
	}

	@Override
	public void onClick(ClickEvent event) {
		//		event.preventDefault();
		fireEvent(event);
	}

	/*inner registrations*/

	public HandlerRegistration addFilteredDragEventHandler(FilteredDragEventHandler handler){
		return handlerManager.addHandler(FilteredDragEvent.TYPE, handler);
	}

	@Override
	public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) {
		return handlerManager.addHandler(MouseDownEvent.getType(), handler);
	}

	@Override
	public HandlerRegistration addMouseUpHandler(MouseUpHandler handler) {
		return handlerManager.addHandler(MouseUpEvent.getType(), handler);
	}

	@Override
	public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
		return handlerManager.addHandler(MouseOutEvent.getType(), handler);
	}

	@Override
	public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) {
		return handlerManager.addHandler(MouseOverEvent.getType(), handler);
	}

	@Override
	public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler) {
		return handlerManager.addHandler(MouseMoveEvent.getType(), handler);
	}

	@Override
	public HandlerRegistration addMouseWheelHandler(MouseWheelHandler handler) {
		return handlerManager.addHandler(MouseWheelEvent.getType(), handler);
	}

	@Override
	public HandlerRegistration addClickHandler(ClickHandler handler) {
		return handlerManager.addHandler(ClickEvent.getType(), handler);
	}

	public int getDragFilterTimeout() {
		return dragFilterTimeout;
	}

	public void setDragFilterTimeout(int dragFilterTimeout) {
		this.dragFilterTimeout = dragFilterTimeout;
	}


}
